/*
 * Copyright (C) 2012-2013  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.util.log.internal;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import nemos.util.i18n.ITranslator;
import nemos.util.i18n.NemosTranslator;
import nemos.util.osgi.FlexibleServiceTracker;

import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;

/**
 * BundleLogger : The bundle-specific logger class.
 * 
 * @author Romain DUBOIS
 */
public class BundleLogger {

    /** The inner loggers */
    private static final Map<Long, BundleLogger> LOGGERS = Collections
            .synchronizedMap(new HashMap<Long, BundleLogger>());

    /** The log messages */
    private static final BlockingQueue<LogMessage> MESSAGES = new LinkedBlockingQueue<LogMessage>();

    /** The closing message */
    private static final LogMessage CLOSE_MSG = new LogMessage(null, 0, null, null, null);

    /**
     * Start the logging thread.
     */
    private static void startThread() {
        final Thread lThread = new Thread(new Runnable() {
            @Override
            public void run() {
                logMessages();
            }
        }, "NemosLoggingThread"); //$NON-NLS-1$
        lThread.setDaemon(true);
        lThread.start();
    }

    /**
     * Get the inner logger for the specified bundle.
     * 
     * @param pBundle
     *            The bundle to log from
     * @return The inner logger
     */
    public static BundleLogger getLogger(final Bundle pBundle) {
        final Long lId = Long.valueOf(pBundle.getBundleId());
        synchronized (LOGGERS) {
            BundleLogger lLogger = LOGGERS.get(lId);
            if (lLogger == null) {
                if (LOGGERS.isEmpty()) {
                    startThread();
                }
                lLogger = new BundleLogger(pBundle);
                LOGGERS.put(lId, lLogger);
            }
            return lLogger;
        }
    }

    /**
     * Remove the logger associated to the provided bundle.
     * 
     * @param pBundle
     *            The bundle to remove the logger of
     */
    public static void removeLogger(final Bundle pBundle) {
        final BundleLogger lLogger;
        synchronized (LOGGERS) {
            lLogger = LOGGERS.remove(Long.valueOf(pBundle.getBundleId()));
            if (LOGGERS.isEmpty()) {
                MESSAGES.offer(CLOSE_MSG);
            }
        }
        if (lLogger != null) {
            lLogger.close();
        }
    }

    /**
     * Log messages.
     */
    static void logMessages() {
        try {
            LogMessage lLog = MESSAGES.take();
            while (lLog != CLOSE_MSG) {
                final LogService lLogger = lLog.getLogger();

                // Only if logger is available
                if (lLogger != null) {
                    lLogger.log(lLog.getService(), lLog.getLevel(), lLog.getMessage(),
                            lLog.getException());
                }

                lLog = MESSAGES.take();
            }
        } catch (final InterruptedException e) {
            System.err.println("Logging thread has been interrupted"); //$NON-NLS-1$
            synchronized (LOGGERS) {
                LOGGERS.clear();
            }
        }
    }

    /** The log service tracker */
    private final FlexibleServiceTracker<LogService, LogService> mLogTracker;
    /** The translator */
    private final NemosTranslator mTranslator;

    /**
     * Constructor
     * 
     * @param pBundle
     *            The bundle to log from
     */
    private BundleLogger(final Bundle pBundle) {
        super();

        mLogTracker = new FlexibleServiceTracker<LogService, LogService>(pBundle, LogService.class);
        mTranslator = new NemosTranslator(pBundle);
        mLogTracker.open();
    }

    /**
     * Get the internal translator.
     * 
     * @return The translator
     */
    public ITranslator getTranslator() {
        return mTranslator;
    }

    /**
     * Log a message.
     * 
     * @param pTranslator
     *            The translator to use.
     * @param pRef
     *            The logging service reference
     * @param pLogLevel
     *            The message log level
     * @param pMessage
     *            The message key
     * @param pException
     *            The exception to log
     * @param pMsgParameters
     *            The message parameters
     */
    public void log(final ITranslator pTranslator, final ServiceReference<?> pRef,
            final int pLogLevel, final String pMessage, final Throwable pException,
            final Object... pMsgParameters) {
        final LogService lLogger = getLogger();

        // No need to translate if no logger service is available
        if (lLogger != null) {

            // Delegate format and translation to the translator
            String lMessage;
            try {
                lMessage = pTranslator.getMessage(pMessage, pMsgParameters);
            } catch (final IllegalFormatException e) {
                // Log invalid format
                MESSAGES.add(new LogMessage(pRef, LogService.LOG_ERROR,
                        "Invalid format : " + pMessage, e, mLogTracker)); //$NON-NLS-1$

                // Get the raw message and append the parameters
                lMessage = pMessage;
                if (pMsgParameters != null && pMsgParameters.length > 0) {
                    lMessage += Arrays.toString(pMsgParameters);
                }
            }

            // Finally, log the message
            MESSAGES.add(new LogMessage(pRef, pLogLevel, lMessage, pException, mLogTracker));
        }
    }

    /**
     * Get the log service.
     * 
     * @return The log service
     */
    public LogService getLogger() {
        return mLogTracker.getService();
    }

    /**
     * Close the bundle logger.
     */
    private void close() {
        mLogTracker.close();
        mTranslator.close();
    }
}
